package com.smartandroid.sa.tag.select;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import com.smartandroid.sa.tag.helper.StringUtil;
import com.smartandroid.sa.tag.nodes.Element;

/**
 * Base combining (and, or) evaluator.
 */
abstract class CombiningEvaluator extends Evaluator {
	final ArrayList<Evaluator> evaluators;
	int num = 0;

	CombiningEvaluator() {
		super();
		evaluators = new ArrayList<Evaluator>();
	}

	CombiningEvaluator(Collection<Evaluator> evaluators) {
		this();
		this.evaluators.addAll(evaluators);
		updateNumEvaluators();
	}

	Evaluator rightMostEvaluator() {
		return num > 0 ? evaluators.get(num - 1) : null;
	}

	void replaceRightMostEvaluator(Evaluator replacement) {
		evaluators.set(num - 1, replacement);
	}

	void updateNumEvaluators() {
		// used so we don't need to bash on size() for every match test
		num = evaluators.size();
	}

	static final class And extends CombiningEvaluator {
		And(Collection<Evaluator> evaluators) {
			super(evaluators);
		}

		And(Evaluator... evaluators) {
			this(Arrays.asList(evaluators));
		}

		@Override
		public boolean matches(Element root, Element node) {
			for (int i = 0; i < num; i++) {
				Evaluator s = evaluators.get(i);
				if (!s.matches(root, node))
					return false;
			}
			return true;
		}

		@Override
		public String toString() {
			return StringUtil.join(evaluators, " ");
		}
	}

	static final class Or extends CombiningEvaluator {
		/**
		 * Create a new Or evaluator. The initial evaluators are ANDed together
		 * and used as the first clause of the OR.
		 * 
		 * @param evaluators
		 *            initial OR clause (these are wrapped into an AND
		 *            evaluator).
		 */
		Or(Collection<Evaluator> evaluators) {
			super();
			if (num > 1)
				this.evaluators.add(new And(evaluators));
			else
				// 0 or 1
				this.evaluators.addAll(evaluators);
			updateNumEvaluators();
		}

		Or() {
			super();
		}

		public void add(Evaluator e) {
			evaluators.add(e);
			updateNumEvaluators();
		}

		@Override
		public boolean matches(Element root, Element node) {
			for (int i = 0; i < num; i++) {
				Evaluator s = evaluators.get(i);
				if (s.matches(root, node))
					return true;
			}
			return false;
		}

		@Override
		public String toString() {
			return String.format(":or%s", evaluators);
		}
	}
}
